-- TreeHouseM
-- Created by cheny3
-- 树屋模块

module("TreeHouseM", package.seeall);

local TREE_HOUSE_ACTIVE_SKILL_NUM = 4;

local treeHouseList = {};
local treeHouseSkills = {};
local treeHouseSkillsByClassId = {};
-- 第一关道具默认的顺序
local treeHouseDefaultItem = {7000, 7288, 7287, 7286};

local whenEnterNext;
local whenLeaveDungeon;

-- 所有的规则处理子模块
local rules = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    treeHouseList = {};
    treeHouseSkills  = {};
    treeHouseSkillsByClassId = {};

    -- tree_house信息
    local tree_house_CSV = get_tree_house_CSV();
    for i = 1, #tree_house_CSV do
        treeHouseList[tree_house_CSV[i].class_id] = tree_house_CSV[i];
    end
    tree_house_CSV = {};

    -- skill表
    local tree_house_skill_CSV = get_tree_house_skill_CSV();
    for i = 1, #tree_house_skill_CSV do
        local row = tree_house_skill_CSV[i];
        treeHouseSkills[row.skill_id] = row;
        treeHouseSkillsByClassId[row.class_id] = row;
    end

    tree_house_skill_CSV = {};
end

function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH2("game/logic/module/tree_house");
    end

    -- 关注进入下一层地牢事件
    EventMgr.removeAll("TreeHouseM");
    EventMgr.register("TreeHouseM", event.NEXT_FLOOR, whenEnterNext);

    -- 关注离开地牢事件
    EventMgr.register("TreeHouseM", event.LEAVE_DUNGEON, whenLeaveDungeon);

    -- 关注回层事件
    EventMgr.register("TreeHouseM", event.BACK_FLOOR, whenEnterNext);

    -- 开始新回合
    CombatM.registerEndRoundCB("tree_house_attack", prepareAttack);

    -- 开始打包验证客户端结果
    EventMgr.register("TreeHouseM", event.START_PACK_VERIFY_RESULT, function(para)
        syncTreeHouseData();
    end);
end

-- 进入下一层
whenEnterNext = function()
    syncTreeHouseData();
end

-- 离开地牢，直接丢弃数据
whenLeaveDungeon = function()
    ME.user.tree_house = nil;
    ME.user.dbase:delete("tree_house");
    ME.user.dbase:delete("pre_tree_house_submit");
end

-- 开始新一层地牢
function whenStartFloor()

end

-- 查询技能规则
function querySkillRule(rule)
    if rules[rule] == "TBL" then
        return LOAD_RUN("game/logic/module/tree_house/" .. rule);
    end

    return rules[rule]
end

-- 第一关默认的物品顺序
function getDefaultList()
    return treeHouseDefaultItem;
end

-- 判断玩家是否有除了默认物品外的东西
function hasSpecialItem()
    local allSkills = getAllSkills();
    local hasItem = false;
    for _, skillInfo in pairs(allSkills) do
        if (ItemM.getAmount(ME.user, skillInfo["class_id"]) > 0 or EquipM.isEquiped(skillInfo["class_id"]))
            and table.indexOf(treeHouseDefaultItem, skillInfo["class_id"]) == -1 then
            hasItem = true;
            break;
        end
    end
    return hasItem;
end

-- 检索
function query(classId, path)
    local m = treeHouseList[classId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 树屋所有技能
function getAllSkills()
    return treeHouseSkills;
end

-- 检索树屋技能
function querySkill(skillId, path)
    local m = treeHouseSkills[skillId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 获取树屋技能相关(根据classId)
function querySkillByItemId(itemId, path)
    local m = treeHouseSkillsByClassId[itemId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 获取树屋发言内容
function getAttackTalk(skillId, args)
    local treeHouse = ME.user.tree_house;

    if args and args.hasRemote then
        -- 远程怪对话
        local classId = treeHouse.dbase:query("class_id");
        local talks   = query(classId, "special_talk")["remote"];
        -- 随机返回一个
        local randomIndex = math.random(1, #talks);
        return talks[randomIndex];
    end

    local talkList = querySkill(skillId, "random_talk");
    local specialTalk = querySkill(skillId, "special_talk");
    local content;

    if treeHouse and type(specialTalk) == "table" then
        -- 有特殊发言
        if treeHouse.dbase:query(specialTalk[1]) == 1 then
            if type(specialTalk[2]) == "table" then
                -- 随机一个
                content = specialTalk[2][math.random(1, #specialTalk[2])];
            else
                content = specialTalk[2];
            end
            -- 删除标记
            treeHouse.dbase:delete(specialTalk[1]);
        end
    end

    if not content then
        -- 随机取一个发言
        local randomIndex = math.random(1, #talkList);
        content = talkList[randomIndex];
    end

    return content;
end

-- 获取树屋数据
function fetchTreeHouseData(classId)
    local treeHouse = ME.user.dbase:query("tree_house");

    if treeHouse then
        -- 树屋信息
        return table.add({
            ["class_id"] = classId,
            ["active"] = 1;
        }, treeHouse or {});
    end

    -- 树屋信息
    return table.add({
        ["class_id"] = classId,
    }, treeHouse or {});
end

-- 消耗
function doCost(cost)
    for key, amount in pairs(cost) do
        if type(key) == "number" then
            ItemM.costAmount(ME.user, key, amount);
        elseif type(key) == "string" then
            ME.user:costAttrib(key, amount);
        end
    end
end

-- 创建一个树屋
function createTreeHouse(classId, info)
    info = info or {};

    if info.skills and info.skills.array then
        info.skills = info.skills:toTable();
    end

    local dbase = table.add({
        ["class_id"] = classId,
        ["skills"] = {},
    }, info);

    local treeHouse = TreeHouse.new(classId, dbase);

    -- 树屋已经激活
    if treeHouse:isActive() then
        ME.user.tree_house = treeHouse;

        -- 记录数据
        ME.user.dbase:set("tree_house", treeHouse:getSaveData());
    end

    return treeHouse;
end

-- 计算升级树屋技能的消耗
function calcUpgradeSkillCost(skillId)
    return FormulaM.invoke("CALC_UPGRADE_TREE_HOUSE_SKILL_COST", ME.user, skillId);
end

-- 判断能否升级树屋技能
function canUpgradeSkill(skillId)
    local treeHouse = ME.user.tree_house;

    if not treeHouse then
        trace("TreeHouseM", "玩家当前没有树屋，不能升级。");
        return false;
    end

    if not treeHouse:ownSkill(skillId) then
        trace("TreeHouseM", "树屋当前没有技能%d，不能升级。", skillId);
        return false;
    end

    local maxLevel = querySkill(skillId, "max_level");
    local curLevel = treeHouse:getSkillLevel(skillId);

    if type(maxLevel) == "number" and curLevel >= maxLevel then
        trace("TreeHouseM", "树屋技能%d满级，不能升级。", skillId);
        return "level_max";
    end

    -- 消耗
    local cost = calcUpgradeSkillCost(skillId);
    local ok = true;
    for classId, amount in pairs(cost) do
        if type(classId) == "number" then
            ok = ItemM.getAmount(ME.user, classId) >= amount;
        elseif type(classId) == "string" then
            ok = ME.user.dbase:query(classId, 0) >= amount;
        end

        if true ~= ok then
            return "cost_lack";
        end
    end

    return ok;
end

-- 升级技能
function upgradeSkill(skillId)
    -- 先判断下
    if true ~= canUpgradeSkill(skillId) then
        return false;
    end

    -- 先做消耗
    local cost = calcUpgradeSkillCost(skillId);
    doCost(cost);

    local treeHouse = ME.user.tree_house;
    treeHouse:upgradeSkill(skillId);

    -- 抛出事件
    EventMgr.fire(event.UPGRADE_TREE_HOUSE_SKILL, { ["skillId"] = skillId, ["treeHouse"] = treeHouse, });

    -- 尝试完成树屋成就
    EventMgr.fire(event.TREE_HOUSE_ATTACK_MATCH, { ["treeHouse"] = treeHouse, });
    EventMgr.fire(event.TREE_HOUSE_LEVEL_MATCH, { ["skillId"] = skillId, ["treeHouse"] = treeHouse, });

    return true;
end

-- 判断能否学习技能
function canLearnSkill(skillId)
    local treeHouse = ME.user.tree_house;
    if not treeHouse then
        trace("TreeHouseM", "没有树屋，不能学习树屋技能。");
        return false;
    end

    if treeHouse:ownSkill(skillId) then
        trace("TreeHouseM", "树屋已经拥有此技能。");
        return false;
    end

    return true;
end

-- 学习技能
function learnSkill(skillId)
    if true ~= canLearnSkill(skillId) then
        return false;
    end

    local treeHouse = ME.user.tree_house;
    treeHouse:learnSkill(skillId);

    return true;
end

-- 能否激活
function canActive(pos)
    -- 如果玩家已经有树屋
    local treeHouse = ME.user.tree_house;
    if treeHouse then
        trace("TreeHouseM", "玩家已经拥有树屋。");
        return false;
    end

    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_TREE_HOUSE then
        trace("TreeHouseM", "不是树屋，无法激活。");
        return false;
    end

    return true;
end

-- 激活树屋
function activeTreeHouse(pos)
    if not canActive(pos) then
        return false;
    end

    local grid = DungeonM.getGridByPos(pos);
    local treeHouse = grid.tree_house;

    cclog("玩家激活树屋");

    local dbase = table.add({
        ["active"] = 1,
    }, fetchTreeHouseData(treeHouse.classId));

    -- 更新数据
    grid.tree_house = treeHouse;
    treeHouse:updateData(dbase);

    -- 攻击回合重置
    treeHouse.action = CombatM.getRound();

    ME.user.tree_house = treeHouse;
    ME.user.dbase:set("tree_house", treeHouse:getSaveData());

    -- 树屋学习技能
    local preTreeHouseSubmit = ME.user.dbase:query("pre_tree_house_submit");
    for _, itemId in pairs(preTreeHouseSubmit) do
        local skillId;
        for _, skillInfo in pairs(treeHouseSkills) do
            if itemId == skillInfo["class_id"] then
                skillId = skillInfo["skill_id"];
                break;
            end
        end

        if skillId then
            -- 学习技能
            learnSkill(skillId);
        end
    end

    -- 抛出事件
    EventMgr.fire(event.ACTIVE_TREE_HOUSE, { ["pos"] = pos, });

    -- 尝试完成树屋成就
    EventMgr.fire(event.TREE_HOUSE_ATTACK_MATCH, { ["treeHouse"] = treeHouse, });

    return true;
end

-- 是否是可以提交的物品
function canSubmitItem(classId)
    if treeHouseSkillsByClassId[classId] then
        return true;
    end
    return false;
end

-- 历史上提交过的物品(用于成就任务)
function querSubmitInHistory()
    return ME.user.dbase:query("submit_in_history");
end

-- 设置提交过的物品(用于成就任务)
function setSubmitInHistory(submitInHistory)
    ME.user.dbase:set("submit_in_history", submitInHistory);
end

-- 树屋激活前的提交
function activeTreeHouseSubmit(pos, classId)
    local preTreeHouseSubmit = ME.user.dbase:query("pre_tree_house_submit") or {};
    if table.indexOf(preTreeHouseSubmit, classId) ~= -1 then
        trace("TreeHouseM", "玩家激活树屋提交重复道具。");
        return false;
    end

    if ItemM.getAmount(ME.user, classId) <= 0 then
        trace("TreeHouseM", "玩家激活树屋道具不足。");
        return false;
    end

    if not canSubmitItem(classId) then
        trace("TreeHouseM", string.format("该道具%s不可提交树屋。", classId));
        return false;
    end

    doCost({[classId] = 1});
    table.insert(preTreeHouseSubmit, classId);
    ME.user.dbase:set("pre_tree_house_submit", preTreeHouseSubmit);

    DungeonLogM.addLog(string.format("玩家树屋提交物品：%s\n", classId));

    -- 找到对应的技能
    local skillId = treeHouseSkillsByClassId[classId]["skill_id"];
    local rule = querySkill(nowSkillId, "rule");
    local ruleObject = querySkillRule(rule);
    if ruleObject then
        -- 特殊子规则，交给子规则处理
        if ruleObject and ruleObject.submitTrigger then
            ruleObject.submitTrigger();
        end
    end

    -- 抛出提交物品事件
    EventMgr.fire(event.SUBMIT_TO_TREE_HOUSE, { ["classId"] = classId });

    if #preTreeHouseSubmit >= TREE_HOUSE_ACTIVE_SKILL_NUM then
        -- 尝试激活树屋
        return activeTreeHouse(pos);
    end

    return true;
end

-- 树屋自动攻击轮次
function autoAttackRound(treeHouse)
    local cd;

    -- 返回cd
    cd = query(treeHouse.classId, "cycle");

    local preTreeHouseSubmit = ME.user.dbase:query("pre_tree_house_submit") or {};

    for _, classId in pairs(preTreeHouseSubmit) do
        local reduceCycle = querySkillByItemId(classId, "reduce_cycle");
        if type(reduceCycle) == "number" then
            cd = cd - reduceCycle;
        end
    end

    return math.max(cd, 1);
end

-- 获取自动攻击轮次倒计时
function getAttackCountDown(treeHouse)
    local round = CombatM.getRound();
    local actionRound = treeHouse.action;
    if not actionRound or actionRound <= 0 then
        actionRound = 1;
    end

    local cycle = autoAttackRound(treeHouse);

    return cycle - (round - actionRound) % cycle;
end

-- 检查特殊回合发言
function checkSpecialTalk(treeHouse)
    local nextSkill = treeHouse.dbase:query("next_skill");

    if not nextSkill then
        local preTreeHouseSubmit = ME.user.dbase:query("pre_tree_house_submit");
        nextSkill = querySkillByItemId(preTreeHouseSubmit[1], "skill_id");
    end

    local dbase = TreeHouseM.querySkill(nextSkill, "dbase");
    local roundTalk = dbase["round_talk"];
    if not roundTalk or type(roundTalk) ~= "table" then
        -- 不是回合发言类型
        return;
    end

    if table.indexOf(roundTalk, getAttackCountDown(treeHouse)) == -1 then
        -- 不是特殊发言回合
        return;
    end

    -- 抛出特殊发言事件
    EventMgr.fire(event.TREE_HOUSE_TALK, {["skillId"] = nextSkill, ["pos"] = treeHouse:getPos()});
end

-- 树屋自动攻击
function prepareAttack()
    -- 获取树屋pos
    local treeHouse = ME.user.tree_house;

    if not treeHouse then
        return false;
    end
    if not treeHouse:isActive() then
        return false;
    end

    local grid = DungeonM.getGridByPos(treeHouse:getPos());
    if grid.type ~= GRID_TYPE_TREE_HOUSE then
        return false;
    end

    -- 玩家已经死亡
    if ME.user:isDead() then
        trace("TreeHouseM", "玩家已经死亡。");
        return;
    end

    -- 当前回合
    local round = CombatM.getRound();
    local actionRound = treeHouse.action;
    if not actionRound or actionRound <= 0 then
        actionRound = 1;
    end

    -- 检查特殊回合发言
    checkSpecialTalk(treeHouse);

    if (actionRound >= round) then
        trace("TreeHouseM", "上次行动回合:%d，当前回合:%d", actionRound, round);
        return false;
    end

    -- 自动攻击回合
    local cycle = autoAttackRound(treeHouse);
    if round - actionRound < cycle or (round - actionRound) % cycle ~= 0 then
        return false;
    end

    -- 先记录行动轮次
    treeHouse.action = round;

    local preTreeHouseSubmit = ME.user.dbase:query("pre_tree_house_submit");
    local skillId = treeHouse:query("next_skill");
    if type(skillId) ~= "number" then
        -- 初始化
        skillId = treeHouseSkillsByClassId[preTreeHouseSubmit[1]]["skill_id"];
        treeHouse.dbase:set("next_skill", skillId);
    end

    -- 计算参数
    local nowSkillId = treeHouse:query("next_skill");
    local skillLevel = treeHouse:getSkillLevel(nowSkillId);
    local nowSkillItem = treeHouseSkills[nowSkillId]["class_id"];

    DungeonLogM.addLog(string.format("树屋技能%d", nowSkillId));

    local pos = 1;
    local rule = querySkill(nowSkillId, "rule");
    local ruleObject = querySkillRule(rule);
    if ruleObject then
        -- 特殊子规则，交给子规则处理
        if ruleObject.trigger then
            ruleObject.trigger(treeHouse);
        end
    end

    if TreeHouseM.querySkill(nowSkillId, "rule") == "up_power" then
        -- 紧箍设置特殊发言
        treeHouse.dbase:set("relieve", 1);
    end

    local target;

    -- 是攻击道具才走通用流程
    if querySkill(nowSkillId, "attack_property") == 1 then
        -- 通用流程
        -- 目标
        local hasRemote = false;
        -- 攻击怪物，随机一个怪物
        -- 随机选取一个目标
        local grids = {};
        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);
            if grid:isOpened() and grid:isMonster() and not grid.monster:isDead() then
                table.insert(grids, grid);
            end

            if grid:isMonster() and not grid.monster:isDead() and grid.monster:isRemoteMonster() then
                hasRemote = true;
            end
        end

        if #grids <= 0 then
            trace("TreeHouseM", "树屋没有攻击目标。");
            -- 抛出攻击事件
            EventMgr.fire(event.TREE_HOUSE_ATTACK, { ["pos"] = treeHouse:getPos(),
                ["result"] = false, ["hasRemote"] = hasRemote});

            return false;
        end

        -- 随机一个
        local rand = DungeonM.getRandSeed("tree_house_attack");
        local grid = grids[rand % #grids + 1];
        target = grid.monster;


        -- 如果目标已经死亡了
        if target:isDead() then
            return false;
        end

        -- 计算使用道具参数
        local paras = FormulaM.invoke("CALC_TREE_HOUSE_PROPERTY_ARGS", treeHouse, nowSkillId, skillLevel);

        -- 使用道具
        PropertyM.treeHouseApply(treeHouse, target, nowSkillItem, paras);

        -- 触发伤害技能后
        -- 一些需要删除的属性
        deleteTreeHouseProp(treeHouse);
    end

    -- 计算下一次使用技能
    local index;
    local nextItem;
    local nextSkill;

    for index = 1, #preTreeHouseSubmit do
        if preTreeHouseSubmit[index] == treeHouseSkills[nowSkillId]["class_id"] then
            nextItem = preTreeHouseSubmit[index % 4 + 1];
            nextSkill = treeHouseSkillsByClassId[nextItem]["skill_id"];
            break;
        end
    end

    treeHouse.dbase:set("next_skill", nextSkill);

    local infos = treeHouse:getSaveData();
    ME.user.dbase:set("tree_house", infos);

    -- 抛出攻击事件
    EventMgr.fire(event.TREE_HOUSE_ATTACK, { ["pos"] = treeHouse:getPos(), ["result"] = true, ["skillId"] = nowSkillId, ["target"] = target});

    return true;
end

-- 删除部分属性
function deleteTreeHouseProp(treeHouse)
    local allSkills = treeHouse:getAllSkills();
    for skillId,_ in pairs(allSkills) do
        local rule = querySkill(skillId, "rule");
        local ruleObject = querySkillRule(rule);
        if ruleObject then
            if ruleObject.clear then
                ruleObject.clear(treeHouse);
            end
        end
    end
end

function syncTreeHouseData()
    local treeHouse = ME.user.tree_house;
    if treeHouse then
        local infos = treeHouse:getSaveData();
        -- 行动置空
        infos.action = nil;
        ME.user.dbase:set("tree_house", infos);
    end
end
